Глава 10
ВАРИАНТЫ
10.1. ОСНОВНЫЕ СВОЙСТВА ВАРИАНТА
Вариант (в Delphi 1 он отсутствует) - это тип variant, разработанный специально для тех случаев, когда на этапе компиляции программист не может сказать, какого типа данные будут использоваться в выражении или как параметры вызова подпрограмм. Переменная-вариант занимает в памяти дополнительные 2 байта, в которые помещается информация о действительном типе переменной. Эта информация позволяет компилятору создать код, который будет осуществлять необходимое преобразование типов на этапе прогона программы.
В переменную-вариант можно поместить:
Варианты могут участвовать в целочисленных, вещественных, логических и время-дата выражениях при условии корректности соответствующих преобразований. Например, если варианту v присвоена строка '1.0', то выражение 1+v будет правильным вещественным значением 2,0. Однако если v := 'текст', выражение 1+v вызовет исключение EVariantError.
В Delphi определены такие константы, указывающие тип помещенных в вариант данных:
Таблица 10.1. Типы возможных значений варианта
Имя |
Константа |
Смысл |
|
varEmp.ty |
$0000 |
Нет данных |
|
varNull |
$0001 |
Неизвестный тип параметра |
varSmallInt |
$0002 |
Целый тип Smallint |
varlnteger |
$0003 |
Целый тип Integer |
|
varSingle |
$0004 |
Вещественный тип Single |
|
varDouble |
$0005 |
Вещественный тип Double |
|
varCurrency |
$0006 |
Вещественный тип Currency |
|
varDate |
$0007 |
Тип дата-время |
|
varOleStr |
$0008 |
OLE-строка в кодировке Unicode |
|
varDispatch |
$0009 |
Указатель на OLE-объект |
|
varError |
$000А |
Код ошибки |
|
varBoolean |
$000В |
Тип WordBool |
|
varVariant |
$000С |
Тип Variant (только для вариантных массивов) |
|
varUnknow |
$0011 |
Неизвестный OLE-объект |
|
varByte |
$0100 |
Целый тип Byte |
|
varString |
$0100 |
Строковый тип |
|
varArray |
$2000 |
Вариантный массив |
|
varByRef |
$4000 |
Указатель на данные |
Структура вариантного типа описывается следующим образом:
TVarData = packed record
VType: Word;
Reservedly Reserved2, ReservedS: Word;
case Integer of
varSmallInt: (VSmallInt: Smallint);
varlnteger: (VInteger: Integer);
varSingle: (VSingle: Single);
varDouble: (VDouble: Double);
varCurrency: (VCurrency: Currency);
varDate:(VDate: Double) ;
varOleStr: (VOleStr: PWideChar) ;
varDispatch: (VDispatch: Pointer);
varError: (VError: WordBool);
varString: (VString: Pointer);
varArray: (VArray: PVarArray) ;
varByRef: (VPointer: Pointer);
end;
Как нетрудно убедиться, любая переменная вариантного типа представляет собой 16-байтную запись, содержащую 8-байтную вариантную часть, которая хранит либо собственно данные, либо их адрес (т. е. указатель на динамически размещ
К чему приводится |
Тип данных в варианте |
|||||
varEmpty |
Целые |
Вещественные |
Дата- Время |
Строковые |
Логические |
|
К дата- 'У: время |
30.12. 1899 00:00:00 |
Преобразование в Double |
Преобразование в Double |
Без преобразования | Преобразование в дату |
Преобразование в Double |
К целым |
0 |
Преобразование в соответствующий тип |
Округление до ближайшего целого |
Округление до ближайшего целого |
Преобразование в целый тип |
0 для False, Byte) |
.'К дата- 'У: время |
30.12.1899 00:00:00 |
Преобразование в Double |
Преобразование в Double |
Без преобразования |
Преобразование в дату |
Преобразование в Double |
К строковым |
Пустая строка |
Преобразование в символьный вид |
Преобразование в символьный вид |
Преобразование в символьный вид |
Без преобразования |
'0'для False,'-!' для True |
К логическим |
False |
False для 0, иначе True |
False для 0, иначе True |
False для 0, иначе True |
False для 'False' и для '0', иначе True |
Без преобразования |
енные данные). В поле VType в момент создания варианта компилятор помещает, признак отсутствия данных varEmpty. В работающей программе значение этого поля меняется в соответствии с текущим типом данных, размещенных в вариантной части. Замечу, что программа не может получить прямого доступа к полям вариантной записи. Получить тип вариантных данных можно с помощью функции varType (см. ниже), а изменить тип - путем присваивания варианту нового значения.
10.2. ПРЕОБРАЗОВАНИЕ ВАРИАНТОВ К ДАННЫМ ДРУГИХ ТИПОВ
При участии вариантов в выражениях, а также при присваивании их значений переменным других типов тип размещенных в варианте данных преобразуется по следующим правилам:
Таблица 10.2. Преобразование типов для вариантов
Здесь
К целым Отнесены varByte, varSmallInt, varlnteger/ varError;
К вещественным — varSingle, varDouble/ varCurrency;
К строковым -var String, varOleStr.
10.3. ПОДПРОГРАММЫ ДЛЯ РАБОТЫ С ВАРИАНТАМИ
Для работы с вариантами можно использовать такие подпрограммы:
Таблица 10.3. Подпрограммы для работы с вариантами
function VarAsType(const V: Variant; VarType: Integer): Variant; |
Преобразует данные варианта V к типу, определяемому параметром VarType |
procedure VarCast(var Dest: Variant; const Source: Variant; Var Type: Integer) ; |
Преобразует данные варианта Source к типу,определяемому параметром VarType, и помещает результат в переменную Dest |
procedure VarClear(var V: Variant) ;
|
Освобождает динамическую память, если она была связана с вариантом, и дает ему тип varEmpty |
procedure VarCopy(var Dest: Variant; const Source: Variants; |
Копирует параметр Source в вариант Dest |
function VarFrom-DateTime(DateTime: TDateTime):Variant; |
Возвращает вариант, содержащий данные DateTime типа дата-время |
function VarIsEmpty(const V: Variant): Boolean; |
Возвращает True, если вариант V не содержит данных |
function VarIsNull(const V: Vari ant) : Boolean; |
Возвращает True, если вариант V содержит данные неопределенного типа (varNull) ', |
function VarToDateTime(const V: Variant): TDateTime) ; |
Преобразует данные варианта V к типу дата-время |
function VarToStr(const V: Vari ant) : String; |
Преобразует данные варианта V к строке ; |
function VarType(const V: Variant) : Integer; |
Возвращает тип хранящихся в варианте данных i |
10.4. ВАРИАНТНЫЕ МАССИВЫ
Значением варианта может быть массив данных, такие варианты называются вариантными массивами. (Не путайте с обычным или динамическим массивом, элементами которого являются варианты!) Значениями элементов вариантного массива могут быть любые допустимые для варианта значения, кроме строк varstring. Значениями элементов вариантного массива могут быть и варианты, а это значит, что в таком массиве могут одновременно храниться данные разных типов (и в том числе строки). Например:
var
V: Variant;
begin
// Создаем одномерный вариантный массив с 5 элементами:
V := VarArrayCreate([0, 4], varVariant);
// Наполняем его:
V[0] := 1; //Тип целый
V[1] := 1234.5678; //Тип вещественный
V[2] := 'Hello world'; //Строковый тип
V[3] := True; //Логический тип
//Пятым элементом исходного массива сделаем еще один массив:
V[4] := VarArrayOf([l, 10, 100, 1000]);
Caption := V[2]; //Hello world
IbOutput.Caption := IntToStr(V[4][2]); //200
end;
Все действия с вариантными массивами осуществляются с помощью следующих процедур и функций:
Таблица 10.4. Подпрограммы для работы с вариантными массивами
function VarArrayCreate(const Bounds: array of Integer; VarType: Integer): Variant; |
Создает вариантный массив из элементов типа VarType с количеством и границами измерений, указываемых параметром Bounds |
function VarArrayDimCount(const A: Variant): Integers; |
Возвращает количество измерений вариантного массива А или 0, если А не массив |
function VarArrayHighBound(const A: Variant; Dim: Integer): Integer; |
Возвращает верхнюю границу индекса вариантного массива А по измерению Dim |
function VarArrayLock(var A: Variant): Pointer;
|
Блокирует массив (предотвращает его возможные изменения размеров) и возвращает указатель на связанные с ним данные |
function VarArrayLowBound(const A: Variant; Dim: Integer): Integers; |
Возвращает нижнюю границу индекса вариантного массива А по измерению Dim |
function VarArrayOf(const Values: array of Variant): Variants;
|
Создает одномерный вариантный массив по перечню значений, содержащихся в открытом массиве Values. Нижняя граница индексов вариантного массива в этом случае равна 0 |
procedure VarArrayRedim(var A: Variant; HighBound: Integer) ;
|
Изменяет верхнюю границу индекса вариантного массива А на величину HighBound. Вызов про цедуры игнорируется, если массив был заблоки рован функцией VarArrayLock |
function VarArrayRef(const A: Variant): Variants; |
Возвращает ссылку на вариантный массив. Ис пользуется при обращении к API-функциям |
procedure VarArrayUnlock(var A: Variant) |
Отменяет действие функции VarArrayLock
|
10.5. ПОЛЬЗОВАТЕЛЬСКИЕ ВАРИАНТЫ
Стандартный вариант может хранить только одно из значений, указанных в табл. 10.2. В версии Delphi 6 появились так называемые пользовательские варианты, которые фактически снимают ограничения на характер значений варианта.
Чтобы познакомиться со свойствами новых вариантов, воспользуемся одним из них - вариантом, способным хранить комплексные числа, преобразовывать их в другие типы и осуществлять над ними нужные действия. Как мы увидим дальше, создание пользовательского варианта может быть весьма трудоемким делом - все зависит от сложности хранимых в нем данных. Мы воспользуемся вариантом, созданным разработчиками Delphi и включенным в модуль
VarCmplx.
Создайте такой обработчик bbRunClick:
uses VarCmplx; // Эта ссылка обязательна!
procedure TfmExample.bbRunClick(Sender: TObject);
var
VI, V2: Variants-begin
// Создаем два случайных комплексных числа:
VI := VarComplexCreate(Trunc(Random*1000)/100,
Trunc(Random*1000)/100) ;
V2 := VarComplexCreate(Trunc(Random*1000)/100,
Trunc(Random*1000)/100) ;
with mmOutput.Lines do
begin
// Пустая строка-разделитель
Add ( ' ' ) ;
Add('1-e число: '# 9+V1) ;
Add('2-е число: '#9+V2);
Add('Сложение'#9+(V1+V2));
Add('Вычитание'#9+(V1-V2));
Add('Умножение'# 9+(VI*V2)) ;
Add('Деление'#9#9+(V1/V2))
end
end;
Небольшой комментарий: сложная конструкция Trunc (Random*1000) /100 понадобилась только для того, чтобы реальные и мнимые части комплексных чисел содержали по три значащих цифры.
Вид экрана работающей программы показан на рис. 10.1. Как видим, новый вариант легко справляется с поддержкой комплексных чисел: функция VarComplexCreate создает вариант, содержащий комплексное число, а дальнейшее поведение варианта -стандартное (он поддерживает математические операции и преобразование к строковому типу). Однако эта легкость обманчива: исходный текст модуля VarCmplx, который, собственно, и придал варианту дополнительные свойства (по умолчанию располагается в файле Source\Rtl\Common\VarCmplx.pas), содержит более 30000 байт..
На с. 229 показана структура записи TVarData. Два первых байта в этой записи (поле VType) хранят признак значения варианта, остальные 14 могут использоваться для размещения данных.
Рис. 10.1. Демонстрация комплексных вариантов
Создание пользовательского варианта проходит в три этапа.
В результате перечисленных шагов вы получаете полноценный вариант, обогащенный новыми свойствами: он может хранить не только те значения, которые перечислены в табл. 10.2, но и любые другие, в том числе свойства и методы! (В этом последнем случае наследником для исполняемого класса нового варианта вместо TCustomVariantType является TInvokeableVariantType или TPublishableVariantType.)
10.5.1. Размещение в варианте новых значений
Для размещения в варианте нового (не предусмотренного стандартным вариантом) значения нужно создать соответствующий класс и поместить в подходящее поле rvarData объект этого класса. Вот как, например, размещаются комплексные данные в модуле VarCmplx:
TComplexVarData = packed record
VType: TVarType;
Reserved1, Reserved2, Reserved3: Word;
VComplex: TComplexData;
Reserved4: Longint;
end;
Такая запись лишь сохраняет 16-байтную структуру TVarData, помещая в поле VComplex ссылку на объект класса TComplexData. Собственно комплексные числа хранятся в полях достаточно сложного класса:
type
TComplexData = class(TPersistent) private
FReal, FImaginary: Double;
end;
В этом классе предусмотрены многочисленные методы, управляющие новыми данными. Так, простой вызов VarComplexCreate
приводит к срабатыванию нескольких методов, создающих объект VComplex и наполняющих его поля:
procedure VarComplexCreateInto(var ADest: Variant;
const AComplex: TComplexData);
begin
VarClear(ADest);
TComplexVarData(ADest).VType := VarComplex;
TComplexVarData(ADest).VComplex := AComplex;
end; function VarComplexCreate(const AReal, AImaginary: Double):
Variant;
begin
VarComplexCreateInto(Result,
TComplexData.Create(AReal, AImaginary)) ;
end;
(CM. файл Source\Rtl\Common\VarCmplx.pas).
Запись в которой размещаются новые данные или ссылка на поддерживающий их обьект, должно обьявляться как packed record.
10.5.2. Создание наследника TCustomVariantType
Тип TCustomVariantType или его ближайшие Наследники TPublishableVariantType и TInvokeableVariantType Содержат методы и
свойства, которые в нужный момент вызывают методы и свойства объекта VComplex для осуществления тех или иных преобразований. В модуле varcmpix объявляется такой класс:
type
TComplexVariantType =
class(TPublishableVariantType, IVarStreamable) protected
function LeftPromotion(const V: TVarData;
const Operator: TVarOp;
out RequiredVarType: TVarType): Boolean; override;
function RightPromotion(const V: TVarData;
const Operator: TVarOp;
out RequiredVarType: TVarType): Boolean; override;
function Getlnstance(const V: TVarData): TObject; override;
public
procedure Clear(var V: TVarData);
override;
function IsClear(const V: TVarData): Boolean; override;
procedure Copy(var Dest: TVarData;
const Source: TVarData;
const Indirect: Boolean);
override;
procedure Cast(var Dest:
TVarData;
const Source: TVarData);
override;
procedure CastTo(var Dest: TVarData;
const Source: TVarData;
const AVarType: TVarType);
override;
procedure BinaryOp(var Left: TVarData;
const Right: TVarData;
const Operator: TVarOp); override;
procedure UnaryOp(var Right: TVarData;
const Operator: TVarOp);
override;
function CompareOp(const Left: TVarData;
const Right: TVarData;
const Operator: Integer): Boolean;
override;
procedure Streamin(var Dest: TVarData;
const Stream: TStream) ;
procedure StreamOut(const Source: TVarData;
const Stream: TStream) ;
end;
Обратите внимание: класс TComplexVariantType - интерфейсный (см. п. 9.4.1). Помимо общих для варианта методов он реализует также два метода, специфичных для интерфейса Ivarstreamabie -Streamin и StreamOut, с помощью которых значения нового интерфейса сохраняются в потоке и считываются из него.
Задача этого класса - дать единообразные команды, способные интерпретироваться объектом vcomplex как команды преобразования типа хранящихся данных, их сравнения, реализации над ними тех или иных операций, наконец, записи их в поток и чтения из него. Например, метод cast этого класса вызывается для преобразования других типов значений к комплексному типу, метод castTo - для обратного преобразования, метод BinaryOp реализует бинарную операцию, a Unarydp - унарную и т. д.
Еще раз подчеркну, что основная работа (например, по выполнению бинарных операций) реализуется методами класса TComplex-Data. Класс TCompiexVariantType перекрывает абстрактные методы своего родителя, подключая TComplexData к решению той или иной проблемы.
Поскольку для создания экземпляра нового варианта необходим уже готовый экземпляр (объект) класса TCompiexVariantType, он создается в секции инициализации модуля varcmpix и уничтожается в завершающей секции:
initialization
ComplexVariantType := TCompiexVariantType.Create;
finalization
FreeAndNil(ComplexVariantType);
10.5.3. Создание вспомогательных методов
Несмотря на интенсивное использование классов TCompiexData и TCompiexVariantType, эти классы в конечном счете остаются скрытыми от пользователя нового варианта за счет набора вспомогательных
методов, таких как VarComplexCreate, VarIsComplex, VarAsComplex и т.
п., которые преобразуют обычные процедурные вызовы в вызовы методов и обращения к свойствам соответствующих классов.